package com.whz.gateway.configure.grey;

import com.alibaba.fastjson.JSON;
import com.whz.generic.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 灰度发布负载均衡器,灰度服务metadata中有灰度标记(业务版本号):
 * <p>
 * 1) 此处通过匹配request header中的版本号
 * <p>
 * 2) 如果是蓝绿发布,此处可以(随机选择/后台人工分配)指定用户的版本号信息
 * <p>
 * 匹配后台服务的版本号信息
 *
 * @author whz
 */
@Slf4j
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final String serviceId;
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final AtomicInteger position = new AtomicInteger(new Random().nextInt(1000));

    public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.serviceId = serviceId;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(final Request request) {
        if (serviceInstanceListSupplierProvider != null) {
            ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                    .getIfAvailable(NoopServiceInstanceListSupplier::new);
            return supplier.get().next().map(instances -> getInstanceResponse(instances, request));
        }
        NoopServiceInstanceListSupplier supplier = new NoopServiceInstanceListSupplier();
        return supplier.get().next().map((instances -> getInstanceResponse(instances, request)));
    }

    private Response<ServiceInstance> getInstanceResponse(
            List<ServiceInstance> instances, Request request) {
        if (instances.isEmpty()) {
            log.error("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        HttpHeaders headers = (HttpHeaders) request.getContext();
        List<String> value = headers.get(Constants.GREY_HEAD_KEY);
        if (!CollectionUtils.isEmpty(value)) {
            log.info("current grey header:{}", JSON.toJSONString(value));
            Collections.shuffle(instances);
            for (ServiceInstance instance : instances) {
                String v = instance.getMetadata().get(Constants.GREY_FLAG);
                if (value.contains(v)) {
                    return new DefaultResponse(instance);
                }
            }
            log.warn("current grey header:{},but not found matched service", JSON.toJSONString(value));
        }
        List<ServiceInstance> temp = instances.stream()
                .filter(s -> !s.getMetadata().containsKey(Constants.GREY_FLAG))
                .collect(Collectors.toList());

        if (log.isDebugEnabled()) {
            log.debug(" ===== {} services found and filter {} of them with grey flag", instances.size(), (instances.size() - temp.size()));
        }

        if (CollectionUtils.isEmpty(temp)) {
            log.error("no services found,please look at nacos config,is there any one or with 'tongyu-grey-flag'?");
            return new EmptyResponse();
        }
        return random(temp);
    }

    private Response<ServiceInstance> random(
            List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }
}